package core

import (
	"fmt"
	"html/template"
	"net/http"
	"net/url"
	"path"
	"regexp"
	"strings"

	"codeberg.org/vnpower/pixivfe/v2/audit"
)

type HTML = template.HTML

// RequestContext encapsulates *audit.Auditor and *http.Request.
type RequestContext struct {
	auditor *audit.Auditor
	request *http.Request
}

// Thumbnails holds different size variants of an artwork image.
//
// Each variant is generated from the original image URL using specific dimension and path transformations.
//
// The thumbnail URLs follow this pattern:
// https://i.pximg.net/c/{width}x{height}[_{quality}]/img-master/img/YYYY/MM/DD/HH/MM/SS/{id}_p0_square1200.jpg
//
// Size variants are generated by modifying the size-quality segment:
//   - Tiny:     250x250_80_a2
//   - Small:    360x360_70
//   - Medium:   600x600
//   - Large:    1200x1200
//   - Master:   1200px width, preserves original aspect ratio by using "master1200" instead of "square1200"
//   - Original: Full-size source image obtained by removing the size-quality segment, replacing
//     "img-master" with "img-original", and stripping the "_square1200" suffix
//
// NOTE: While these thumbnails use "img-master" and "square1200" in their paths,
// i.pximg.net also appears to serve thumbnails using "custom-thumb" and "custom1200".
// Further investigation is needed to determine if this represents different thumbnails
// or just alternative URL patterns.
type Thumbnails struct {
	Tiny     string // 250x250 thumbnail
	Small    string // 360x360 thumbnail
	Medium   string // 600x600 thumbnail
	Large    string // 1200x1200 thumbnail
	Master   string // Original aspect ratio, width limited to 1200px
	Original string // Full-size original artwork
}

func (work *ArtworkBrief) PopulateThumbnails(auditor *audit.Auditor) error {
	thumbnails, err := PopulateThumbnailsFor(auditor, work.Thumbnail)
	if err != nil {
		return err
	}
	work.Thumbnails = thumbnails
	return nil
}

func (r *Ranking) PopulateThumbnails(auditor *audit.Auditor) error {
	var errs []error
	for i := range r.Contents {
		err := r.Contents[i].PopulateThumbnails(auditor)
		if err != nil {
			errs = append(errs, fmt.Errorf("content at index %d: %w", i, err))
		}
	}
	if len(errs) > 0 {
		return fmt.Errorf("failed to populate thumbnails: %v", errs)
	}
	return nil
}

func (content *RankingContent) PopulateThumbnails(auditor *audit.Auditor) error {
	thumbnails, err := PopulateThumbnailsFor(auditor, content.Thumbnail)
	if err != nil {
		return err
	}
	content.Thumbnails = thumbnails
	return nil
}

// Precompiled regex
var (
	// match the "/c/{parameters}/" segment
	sizeQualityRe = regexp.MustCompile(`/c/[^/]+/`)

	// match any suffix that starts with an underscore and is
	// followed by non-slash characters before the file extension
	filenameSuffixRe = regexp.MustCompile(`_[^/_]+\.(jpg|png|jpeg)$`)
)

// PopulateThumbnailsFor is a helper function that populates all thumbnail sizes, including Original.
func PopulateThumbnailsFor(auditor *audit.Auditor, thumbnailURL string) (Thumbnails, error) {
	var thumbnails Thumbnails

	// auditor.SugaredLogger.Debugf("PopulateThumbnails called with Thumbnail URL: %s", thumbnailURL)

	// Parse the original Thumbnail URL to ensure it's valid
	parsedURL, err := url.Parse(thumbnailURL)
	if err != nil {
		return thumbnails, fmt.Errorf("invalid Thumbnail URL '%s': %w", thumbnailURL, err)
	}

	// Verify that the Thumbnail URL contains the expected pattern
	if !sizeQualityRe.MatchString(parsedURL.Path) {
		auditor.SugaredLogger.Warnf("Thumbnail URL does not match expected pattern: %s. Using original URL for all thumbnail sizes.", thumbnailURL)
		thumbnails.Tiny = thumbnailURL
		thumbnails.Small = thumbnailURL
		thumbnails.Medium = thumbnailURL
		thumbnails.Large = thumbnailURL
		thumbnails.Master = thumbnailURL
		thumbnails.Original = thumbnailURL
		return thumbnails, nil
	}

	// Define the desired sizes for the thumbnails along with corresponding fields
	thumbSizes := []struct {
		name  string
		size  string
		field *string
	}{
		{"Tiny", "250x250_80_a2", &thumbnails.Tiny},
		{"Small", "360x360_70", &thumbnails.Small},
		{"Medium", "600x600", &thumbnails.Medium},
		{"Large", "1200x1200", &thumbnails.Large},
	}

	// Iterate over each size and generate the corresponding URL
	for _, thumb := range thumbSizes {
		finalURL, err := generateThumbnailURL(parsedURL, sizeQualityRe, thumb.size)
		if err != nil {
			return thumbnails, fmt.Errorf("error generating thumbnail URL for size %s: %w", thumb.size, err)
		}
		*thumb.field = finalURL
	}

	// Construct the Master URL
	masterURL := strings.Replace(thumbnails.Large, "square1200", "master1200", 1)
	thumbnails.Master = masterURL

	// Construct the Original URL
	originalURL := generateOriginalURL(parsedURL, sizeQualityRe)
	thumbnails.Original = originalURL

	return thumbnails, nil
}

// generateThumbnailURL constructs a thumbnail URL for a given size.
func generateThumbnailURL(parsedURL *url.URL, re *regexp.Regexp, size string) (string, error) {
	newPath := re.ReplaceAllString(parsedURL.Path, fmt.Sprintf("/c/%s/", size))

	// If the new path is the same as the original, it means either:
	// - The URL is already at the desired size.
	// - The regex did not match, which has been handled earlier in PopulateThumbnailsFor.
	// In both cases, returning the original URL is acceptable.
	if newPath == parsedURL.Path {
		return parsedURL.String(), nil
	}

	updatedURL := *parsedURL // Create a copy of the original URL
	updatedURL.Path = newPath
	return updatedURL.String(), nil
}

// generateOriginalURL constructs the original image URL by removing the size and quality segment,
// replacing 'img-master' with 'img-original', and removing the size suffix from the filename.
func generateOriginalURL(parsedURL *url.URL, re *regexp.Regexp) string {
	// Remove the size and quality segment
	originalPath := re.ReplaceAllString(parsedURL.Path, "/")

	// Replace 'img-master' with 'img-original'
	originalPath = strings.Replace(originalPath, "img-master", "img-original", 1)

	// Remove the size suffix from the filename (e.g., '_square1200' -> '')
	originalPath = filenameSuffixRe.ReplaceAllString(originalPath, ".$1")

	// Clean up the path to ensure it's correctly formatted
	originalPath = path.Clean(originalPath)

	originalURL := *parsedURL
	originalURL.Path = originalPath
	return originalURL.String()
}
